import os
import subprocess
import tkinter as tk
from tkinter import messagebox, ttk
import threading

# 获取音频设备名称
def get_audio_devices():
    command = "pactl list short sources"
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    if result.returncode == 0:
        devices = result.stdout.strip().split("\n")
        audio_devices = []
        for device in devices:
            device_info = device.split()
            if len(device_info) > 1:
                audio_devices.append(device_info[1])  # 获取设备名称
        return audio_devices
    return []

# 获取本地IP地址
def get_local_ip_addresses():
    try:
        result = subprocess.run(
            "hostname -I", shell=True, capture_output=True, text=True
        )
        if result.returncode == 0:
            local_ip = result.stdout.strip().split()[0]
            subnet = '.'.join(local_ip.split('.')[:3])

            command = f"nmap -sn {subnet}.0/24 -n"
            result = subprocess.run(
                command, shell=True, capture_output=True, text=True, check=True
            )
            if result.returncode == 0:
                output = result.stdout.strip()
                ip_addresses = []
                for line in output.splitlines():
                    if "Nmap scan report for" in line:
                        ip_addresses.append(line.split()[-1])
                return ip_addresses
            else:
                print("nmap scan failed!")
        return []
    except subprocess.CalledProcessError as e:
        print(f"Error while running nmap: {e}")
        return []
    except Exception as e:
        print(f"Error: {e}")
        return []

# 获取显示设备（保持不变）
def get_display_device():
    display = os.environ.get("DISPLAY")
    if display:
        return display
    else:
        messagebox.showerror("错误", "无法获取显示设备，请确保你在一个图形化环境中运行。")
        return None

# 启动投屏，并将其放入子线程
def start_screen_cast_thread(audio_device, display_device, ip_address, text_box):
    def target():
        text_box.delete(1.0, tk.END)  # 清空之前的日志
        text_box.insert(tk.END, f"启动投屏，目标 IP: {ip_address}\n")

        try:
            # 设置视频输入流参数，确保帧率为10，分辨率为1920x1080
            ffmpeg_command = [
                'ffmpeg',
                '-f', 'x11grab', '-re','-framerate', '10', '-video_size', '1920x1080', '-i', f'{display_device}+0,0',
                '-f', 'pulse', '-ac', '2', '-i', f'{audio_device}',  # 音频源
                '-c:v', 'libx265', '-b:v', '500k', '-preset', 'ultrafast', '-tune', 'zerolatency', '-pix_fmt', 'yuv420p', 
                '-vf', 'scale=iw*0.5:ih*0.5', '-c:a', 'aac', '-b:a', '64k', '-ar', '44100',
                '-threads', '4', '-f', 'mpegts', f'udp://{ip_address}:8080','-max_muxing_queue_size', '1024' , # 目标IP地址和端口
                '-flush_packets','1'
            ]

            # 执行FFmpeg命令并获取输出
            ffmpeg_process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

            # 将进程对象保存到全局变量
            global screen_cast_process
            screen_cast_process = ffmpeg_process

            stdout, stderr = ffmpeg_process.communicate()

            text_box.insert(tk.END, "投屏中...\n")
            text_box.insert(tk.END, f"FFmpeg 输出:\n{stdout.decode()}\n")
            text_box.insert(tk.END, f"FFmpeg 错误:\n{stderr.decode()}\n")

        except subprocess.CalledProcessError as e:
            text_box.insert(tk.END, f"启动 FFmpeg 失败: {str(e)}\n")
    
    # 启动线程运行目标函数
    thread = threading.Thread(target=target)
    thread.daemon = True  # 设置为守护线程，这样程序退出时线程也会自动结束
    thread.start()

# 停止投屏
def stop_screen_cast():
    global screen_cast_process
    if screen_cast_process:
        try:
            # 通过 terminate() 结束进程
            screen_cast_process.terminate()
            screen_cast_process = None
            output_text.insert(tk.END, "投屏已停止。\n")
        except Exception as e:
            output_text.insert(tk.END, f"停止投屏失败: {str(e)}\n")
    else:
        output_text.insert(tk.END, "没有正在运行的投屏任务。\n")

# 处理启动投屏的按钮点击事件
def on_start_button_click():
    audio_device = audio_device_combobox.get()  # 获取选择的音频设备
    display_device = get_display_device()

    if not audio_device:
        messagebox.showerror("错误", "请选择音频设备")
        return

    if not display_device:
        return  # 如果没有获取到显示设备，退出函数

    ip_address = ip_combobox.get()  # 从选择的 IP 地址中获取

    if not ip_address:
        messagebox.showerror("错误", "请选择目标IP地址")
        return

    # 启动FFmpeg进程并返回进程对象
    start_screen_cast_thread(audio_device, display_device, ip_address, output_text)

# 创建GUI窗口
root = tk.Tk()
root.title("Linux投屏应用")

# 设置窗口大小
root.geometry("600x400")

# 添加标签、输入框和按钮
label = tk.Label(root, text="选择目标IP地址:", font=("Arial", 12))
label.pack(pady=10)

# 获取IP地址列表
ip_addresses = get_local_ip_addresses()
if not ip_addresses:
    messagebox.showerror("错误", "未能获取到网络设备IP")
    root.quit()

# 添加IP地址选择框
ip_combobox = ttk.Combobox(root, font=("Arial", 12), values=ip_addresses, state="readonly", width=20)
ip_combobox.pack(pady=10)
if ip_addresses:
    ip_combobox.set(ip_addresses[0])  # 默

# 获取音频设备列表
audio_devices = get_audio_devices()
if not audio_devices:
    messagebox.showerror("错误", "没有检测到音频设备")
    root.quit()

# 获取最长设备名称长度
max_device_name_length = max(len(device) for device in audio_devices)

# 设置音频设备选择框的宽度
device_combobox_width = max_device_name_length + 5

# 添加音频设备选择框
audio_device_label = tk.Label(root, text="选择音频设备:", font=("Arial", 12))
audio_device_label.pack(pady=10)

audio_device_combobox = ttk.Combobox(root, font=("Arial", 12), values=audio_devices, state="readonly", width=device_combobox_width)
audio_device_combobox.pack(pady=10)
audio_device_combobox.set(audio_devices[0])  # 默认选择第一个设备

start_button = tk.Button(root, text="开始投屏", font=("Arial", 12), command=on_start_button_click)
start_button.pack(pady=10)

# 添加停止投屏按钮
stop_button = tk.Button(root, text="停止投屏", font=("Arial", 12), command=stop_screen_cast)
stop_button.pack(pady=10)

# 添加滚动文本框用于显示FFmpeg日志
output_text = tk.Text(root, font=("Courier New", 10), height=10, wrap=tk.WORD)
output_text.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

# 启动GUI主循环
root.mainloop()
